package tatrus;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

/**
 * <p>Title: Board </p>
 * <p>Description: CSIS 343 Course Project</p>
 * 
 * This class is used to display the actual game as it is
 * being played.  It is responsible for rendering the Shapes
 * as the fall, as well as all Shapes that have come to rest
 * and are subsequently part of the current board.  
 * 
 * @author Nick Brown
 * @email  nicholas.brown@my.uwrf.edu
 * @author John Wagner 
 * @email  john.wagner@my.uwrf.edu
 * 
 * @date November 28th 2013
 *
 */

public class Board extends JPanel
{
	private static final long serialVersionUID = 100L;
	
	public static final int COLUMNS = 10;
	public static final int VISIBLE_ROWS = 20;
	public static final int HIDDEN_ROWS = 2;
	public static final int TOTAL_ROWS = 22;
	public static final int BORDER_WIDTH = 5;
	public static final int SQUARE_SIZE = 25;   
	public static final int BOARD_WIDTH = 260;
	public static final int BOARD_HEIGHT = 510;
	public static final Font BOARD_FONT = new Font("Tahoma", Font.BOLD, 30);
	
	private Game game;
	private Display display;
	private Shape currentShape;
	private Shape[][] boardGrid; 
	private int currentColumn, currentRow;
	private boolean isGameOver;
	
	/**
	 * Creates a new Board used to display the game shapes as they
	 * fall, as well as the current state of the game board.
	 * 
	 * @param game The Game object controlling the game
	 * 
	 * @param display The Display object responsible for displaying
	 * 					information about the current game
	 */
	public Board(Game game, Display display)
	{
		this.game = game;
		this.display = display;
		
		boardGrid = new Shape[TOTAL_ROWS][COLUMNS];  
		isGameOver = false;
		
		setBounds(0, 0, Board.BOARD_WIDTH, Board.BOARD_HEIGHT);
		setBackground(Color.BLACK);
	}
	
	/**
	 * Inherited method of the JPanel class.  Used to render the shapes
	 * as they fall, as well as the current state of the board.
	 * 
	 * @param graphics The Graphics object used for drawing
	 */
	public void paintComponent(Graphics graphics)
	{
		super.paintComponent(graphics);
		
		// Drawing the board perimeter
		graphics.translate(BORDER_WIDTH, BORDER_WIDTH);
		graphics.setColor(Color.WHITE);
		graphics.drawRect(-1, -1, SQUARE_SIZE * COLUMNS + 1,
						      SQUARE_SIZE * VISIBLE_ROWS + 1);
		
		// Drawing the current game board		
		drawBoard(graphics);
		
		// Drawing the current shape in play
		currentShape = game.getCurrentShape();
		drawShape(currentShape, graphics);
		
		// Drawing the game over message if the game has ended
		if(isGameOver)
		{
			graphics.setColor(Color.WHITE);
			
			graphics.setFont(BOARD_FONT);
			
			/* This code allows for determining the size of any particular
			 * piece of font.  This size needs to be know so that the font
			 * can be properly centered on the board.  
			 */
			BufferedImage image;
			image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
			
			FontMetrics metrics; 
			metrics = image.getGraphics().getFontMetrics(BOARD_FONT);
			
			int stringWidth = metrics.stringWidth("Game Over");
			
			graphics.drawString("Game Over", BOARD_WIDTH / 2 - stringWidth / 2,
								BOARD_HEIGHT / 2);
		}
	}
	
	/**
	 * Draws an individual shape as it falls.  Since the shape has not 
	 * become part of the board yet, it must be handled separately. 
	 * 
	 * @param shape The shape to be drawn
	 * 
	 * @param graphics The Graphics object used for drawing
	 */
	private void drawShape(Shape shape, Graphics graphics) 
	{
		if(shape != null && !isGameOver)
		{
			graphics.setColor(shape.getColor());
			
			/* Getting the current shape's rotation grid,
			 * and the current row and column position of
			 * its origin, so that it can be properly drawn.  
			 */
			int [][] shapeGrid = shape.getShapeGrid(shape.getRotationNumber());
			currentColumn = game.getCurrentColumn();
			currentRow = game.getCurrentRow();
			
			int size = shape.getShapeGridSize();		
			
			/* Iterating through all entries in the shape's rotation
			 * grid and only drawing when the entry is a 1, which
			 * indicates a square that makes up the shape.
			 */
			for(int i = 0; i < size; i++)
			{
				for(int j = 0; j < size; j++)
				{
					if(shapeGrid[i][j] == 1 && currentRow - size + i > -1)
					{
						
					   graphics.fillRect((currentColumn + j) * SQUARE_SIZE,
						   		     	 (currentRow - size + i) * SQUARE_SIZE,
						   		     	  SQUARE_SIZE, SQUARE_SIZE);
					}
				}
			}
		}
	}
	
	/**
	 * Draws all shapes and remnants of shapes 
	 * that have become part of the board.
	 * 
	 * @param graphics The Graphics object used for drawing
	 */
	public void drawBoard(Graphics graphics)
	{
		Color color;
		
		/* Iterating through the board grid and drawing 
		 * all shapes that are part of the current board.
		 */
		for(int i = 0; i < TOTAL_ROWS; i++)
		{
			for(int j = 0; j < COLUMNS; j++)
			{
				if(boardGrid[i][j] != null)
				{
					color = boardGrid[i][j].getColor();
					graphics.setColor(color);
					graphics.fillRect(j * SQUARE_SIZE, i * SQUARE_SIZE,
									  SQUARE_SIZE, SQUARE_SIZE);
				}
			}
		}
	}
	
	/**
	 * Determines whether or not a shape can 
	 * move to any given position on the board.  
	 * 
	 * @param shape The current shape in play
	 * 
	 * @param row The row of the board to move to
	 * 
	 * @param column The column of the board to move to
	 * 
	 * @param rotation The current rotation of the shape 
	 * 
	 * @return true if the shape can move to the position
	 * 		   specified by the row and column, otherwise false
	 */
	public boolean shapeCanMove(Shape shape, int row, int column, int rotation)
	{
		// For stopping the I-Shape at the bottom of the board
		if(shape.getType() == 0)
		{
			if(row + shape.getShapeGridSize() - 
					 shape.getLowerOffset(rotation) > 24)
			{
				return false;
			}
		}
		
		// For stopping the I-Shape at the bottom of the board
		else if(shape.getType() == 1)
		{
			if(row + shape.getShapeGridSize() - 
					 shape.getLowerOffset(rotation) > 22)
			{
				return false;
			}
		}
		
		// For stopping all other shapes at the bottom of the board
		else if(row + shape.getShapeGridSize() - 
				      shape.getLowerOffset(rotation) > 23)
		{
			return false;
		}
		
		// For stopping shapes from moving beyond the right edge of the board
		if(column + shape.getShapeGridSize() - 
					shape.getRightOffset(rotation) > 10)
		{			
			return false;
		}
		
		// For stopping shapes from moving beyond the left edge of the board
		if(column + shape.getLeftOffset(rotation) < 0)
		{
			return false;
		}
		
		int[][] currentRotation = shape.getShapeGrid(rotation);
		
		int size = shape.getShapeGridSize();
		
		/* For stopping shapes from moving anywhere on the
		 * board that is already occupied by any part of 
		 * a shape that is currently part of the board.   
		 */
		for(int i = 0; i < size; i++)
		{
			for(int j = 0; j < size; j++)
			{
				if(currentRotation[i][j] == 1 && row - size + i >= 0)
				{
					if(boardGrid[row - size + i][column + j] != null)
					{
						return false;
					}
				}
			}
		}
		
		return true;
	}
	
	/**
	 * Adds a shape to the board once it comes to rest 
	 * on the bottom of the board or another shape.
	 * 
	 * @param shape The shape to add to the board
	 * 
	 * @param row The row that the origin of the shape is on
	 * 
	 * @param column The column that the origin of the shape is on
	 * 
	 * @param rotation The current rotation of the shape
	 */
	public void addShapeToBoard(Shape shape, int row, int column, int rotation)
	{
		int[][] currentRotation = shape.getShapeGrid(rotation);
		
		int size = shape.getShapeGridSize();
		
		/* Adding a shape that has come to rest to the board.
		 * If the shape occupies any space that is above the
		 * top edge of the board, the game ends.
		 */
		for(int i = 0; i < size && !isGameOver; i++)
		{
			for(int j = 0; j < size && !isGameOver; j++)
			{
				if(currentRotation[i][j] == 1)
				{
					if(row - size + i < 0)
					{
						isGameOver = true;
						clearBoard();
						game.stopTimer();
						display.setGameStatus(false);
						display.repaint();
						repaint();
						game.runGameOverSequence();
					}
					
					else
					{
						boardGrid[row - size + i][column + j] = shape;
					}
					
				}
			}
		}
	}
	
	/**
	 * Clears all shapes from the board.  Only used when game
	 * over conditions are reached or a new game is started.
	 */
	public void clearBoard() 
	{
        for(int i = 0; i < TOTAL_ROWS; i++) 
        {
            for(int j = 0; j < COLUMNS; j++) 
            {
            	boardGrid[i][j] = null;
            }
        }
	}
	
	/**
	 * Checks all rows of the board to see if 
	 * any rows have been cleared after the 
	 * current shape in play comes to rest.    
	 * 
	 * @return The number of rows cleared
	 */
	public int checkAllRows()
	{
		int rowCount = 0;
		
		for(int i = 0; i < TOTAL_ROWS; i++) 
        {
            if(rowIsFull(i))
            {
            	rowCount++;
            }
        }
		
		return rowCount;
	}
	
	/**
	 * Determines whether or not an individual 
	 * row is full.  If the row is full, it gets
	 * cleared from the board, and all rows above
	 * it are shifted down by one.  
	 * 
	 * @param row The row of the board to be checked
	 * 
	 * @return true if the row is full, otherwise false
	 */
	public boolean rowIsFull(int row)
	{
		for(int i = 0; i < COLUMNS; i++) 
        {
           if(boardGrid[row][i] == null)
           {
        	   return false;
           }
        }
		
		for(int i = row - 1; i >= 0; i--) 
        {
			for(int j = 0; j < COLUMNS; j++) 
	        {
				boardGrid[i + 1][j] = boardGrid[i][j];
	        }
        }
		
		return true;
	}
	
	/**
	 * Used to set the game over status of 
	 * the game currently being played.  
	 * 
	 * @param status The game over status of the game
	 */
	public void setGameOverStatus(boolean status)
	{
		isGameOver = status; 
	}
}